﻿using System;
using System.Data;
using System.Collections;
using System.Collections.Generic;

namespace BizElements.Core
{
    /// <summary>Creates instances of IEntityDAO objects that best suite given entity objects.</summary>
    /// <remarks>Factory first checks if a custom <see cref="IObjectReader"/> class is registered for the specified entity class.
    /// If it is found then a new instance of the custom class is returned.
    /// Then it checks if entity is a base class mapped to table, i.e. if <see cref="IDbTable.NewEntity()"/> creates exactly the same 
    /// object as the specified entity. If it is then an object created by <see cref="IDbTable.NewEntityReader()"/> is returned.
    /// Finally, if none of the above conditions are met, the factory returns a generic <b>EntityReader</b> object.
    /// </remarks>
    public static class EntityReaderFactory
    {
        static Dictionary<string, Type> CustomEntityReaderMap = new Dictionary<string, Type>();

        /// <summary>Registers custom entity reader class.</summary>
        /// <typeparam name="TEntity">Type of entities which are created by the specified readar class.</typeparam>
        /// <typeparam name="TEntityReader">Entity reader class that created the specified entities.</typeparam>
        public static void RegisterEntityReader<TEntity, TEntityReader>()
            where TEntity : IEntity, new()
            where TEntityReader : IObjectReader, new()
        {
            string entityClassName = typeof(TEntity).FullName;
            CustomEntityReaderMap[entityClassName] = typeof(TEntityReader);
        }

        /// <summary>Creates a new instance of entity reader.</summary>
        /// <typeparam name="TEntity">Type of entities which are to be created from data.</typeparam>
        /// <returns>Entity reader.</returns>
        public static IObjectReader GetEntityReader<TEntity>()
            where TEntity : IEntity, new()
        {
            string entityClassName = typeof(TEntity).FullName;
            if (CustomEntityReaderMap.Count > 0)
            {
                Type customReaderClass;
                CustomEntityReaderMap.TryGetValue(entityClassName, out customReaderClass);
                if (customReaderClass != null)
                    return (IObjectReader)Activator.CreateInstance(customReaderClass);
            }

            IDbTable table = new TEntity().Table;
            string baseEntityClassName = table.NewEntity().GetType().FullName;
            bool isBaseEntity = (entityClassName == baseEntityClassName);
            if (isBaseEntity)
                return table.NewEntityReader();
            else
                return new EntityReader<TEntity>();
        }

        /// <summary>Creates a new instance of entity reader.</summary>
        /// <param name="entityClass">Type of entities which are to be created from data. Must implement <see cref="IEntity"/> interface and must have a default constructor.</param>
        /// <returns>Entity reader.</returns>
        public static IObjectReader GetEntityReader(Type entityClass)
        {
            string entityClassName = entityClass.FullName;
            if (CustomEntityReaderMap.Count > 0)
            {
                Type customReaderClass;
                CustomEntityReaderMap.TryGetValue(entityClassName, out customReaderClass);
                if (customReaderClass != null)
                    return (IObjectReader)Activator.CreateInstance(customReaderClass);
            }

            IDbTable table = ((IEntity)Activator.CreateInstance(entityClass)).Table;
            string baseEntityClassName = table.NewEntity().GetType().FullName;
            bool isBaseEntity = (entityClassName == baseEntityClassName);
            if (isBaseEntity)
            {
                return table.NewEntityReader();
            }
            else
            {
                Type genericEntityReaderClasss = typeof(EntityReader<>).MakeGenericType(entityClass);
                return (IObjectReader)Activator.CreateInstance(genericEntityReaderClasss);
            }
        }

        /// <summary>Creates a new instance of entity reader.</summary>
        /// <param name="destinationCollection">Collection of entities that will be filled with creted entities. 
        /// The method will try to inspect collection's generic arguments to determine the precise type of entities which are to be created.</param>
        /// <param name="prototype">Object that represents the type of entities which are to be created from data. Must have a public constructor.</param>
        /// <returns>Entity reader.</returns>
        public static IObjectReader GetEntityReader(IList destinationCollection, IEntity prototype)
        {
            Type preciseEntityType = GetEntityTypeStoredInCollection(destinationCollection);
            if (preciseEntityType != null)
                return GetEntityReader(preciseEntityType);
            else
                return GetEntityReaderForEntitiesOfTheSameTypeAs(prototype);
        }

        /// <summary>Creates a new instance of entity reader.</summary>
        /// <param name="destinationCollection">Collection of entities that will be filled with creted entities. 
        /// The method will try to inspect collection's generic arguments to determine the precise type of entities which are to be created.</param>
        /// <param name="table">Table. If the method cannot determine the precise entity type based on collection class it will 
        /// fallback to base entities which are mapped to the provided table.</param>        
        /// <returns></returns>
        public static IObjectReader GetEntityReader(IList destinationCollection, IDbTable table)
        {
            Type preciseEntityType = GetEntityTypeStoredInCollection(destinationCollection);
            if (preciseEntityType != null)
                return GetEntityReader(preciseEntityType);
            else
                return GetEntityReaderForEntitiesOfTheSameTypeAs(table.NewEntity());
        }

        private static Type GetEntityTypeStoredInCollection(IList collection)
        {
            Type collectionType = collection.GetType();
            if (collectionType.IsGenericType)
            {
                Type itemType = collectionType.GetGenericArguments()[0];
                if (itemType.GetInterface("IEntity") != null)
                    return itemType;
            }

            return null;
        }

        private static IObjectReader GetEntityReaderForEntitiesOfTheSameTypeAs(IEntity prototype)
        {
            Type entityClass = prototype.GetType();
            string entityClassName = entityClass.FullName;
            if (CustomEntityReaderMap.Count > 0)
            {
                Type customReaderClass;
                CustomEntityReaderMap.TryGetValue(entityClassName, out customReaderClass);
                if (customReaderClass != null)
                    return (IObjectReader)Activator.CreateInstance(customReaderClass);
            }

            IDbTable table = prototype.Table;
            string baseEntityClassName = table.NewEntity().GetType().FullName;
            bool isBaseEntity = (entityClassName == baseEntityClassName);
            if (isBaseEntity)
            {
                return table.NewEntityReader();
            }
            else
            {
                Type genericEntityReaderClasss = typeof(EntityReader<>).MakeGenericType(entityClass);
                return (IObjectReader)Activator.CreateInstance(genericEntityReaderClasss);
            }
        }                
    }
}
